# 使用keras构建神经网络


import argparse

import keras
import matplotlib.pyplot as plt
import numpy as np
from keras.datasets import mnist
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from keras.utils import np_utils


# y=3x
def fun1():
    # 1.定义数据集
    x = np.array([[1], [2], [3], [7]])
    y = np.array([[3], [6], [9], [21]])

    # 2.实例化一个可以顺序计算的神经网络模型,可以在其中堆叠多个网络层，计算过程按网络层的堆叠顺序进行。
    # Sequential方法能够构建顺序计算模型
    model = keras.models.Sequential()

    # 3.向模型添加一个Dense层(全连接层).Dense层用于模型各个层之间的全连接（上一层的每个节点与本层的每个节点间都有连接)
    # 需要确保为模型提供输入形状（由于这是第一个全连接层，因此需要指定模型期望接受的数据形状) 隐藏层中有三个节点，并且在隐藏层中使用的激活函数是relu函数
    model.add(Dense(3, activation='relu', input_shape=(1,)))
    # 4.将隐藏层连接到输出层
    # 在这一层中，我们无需指定输入形状，因为模型可以从上一层中推断出输入形状。输出层有一个节点，并使用线性激活函数
    model.add(Dense(1, activation='linear'))
    # 模型概要信息可视化输出
    # param=（输入神经元个数+1）*神经元个数 +1时因为考虑到每个神经元都有一个Bias
    model.summary()

    # 5.编译模型 首先，需要定义损失函数和优化器，以及优化器相对应的学习率
    sgd = SGD(learning_rate=0.01)
    # 均方误差损失函数
    model.compile(optimizer=sgd, loss='mean_squared_error')

    # 6.拟合模型，更新权重，以优化模型
    # epochs:训练数据集的次数 batch_size:每次权重更新的迭代中训练的数据量大小
    # verbose:指定训练过程中的输出信息。 0:不输出日志信息 1：带进度条的输出日志信息 2:为每一个epoch输出一行记录,没有进度条
    model.fit(x, y, epochs=10, batch_size=4, verbose=1)

    # 7.提取权重值
    print(model.weights)
    # 仅提取权重的值 以数组列表的形式显示
    print(model.get_weights())

    x1 = [[5], [6]]
    output = model.predict(x1)
    print(output)


# 训练香草神经网络
def fun2():
    # 1.数据 x:图片 y:标签
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    # 2.显示前几张示例图片 28x28=784
    plt.subplot(221)
    plt.imshow(x_train[0], cmap='gray')  # 2行2列 第一个
    plt.subplot(222)
    plt.imshow(x_train[1], cmap='gray')  # 2行2列 第2个
    plt.subplot(223)
    plt.imshow(x_test[0], cmap='gray')  # 2行2列 第3个
    plt.subplot(224)
    plt.imshow(x_test[1], cmap='gray')  # 2行2列 第4个
    plt.show()

    # 3.展平 28x28图像，以便将输入变换为一维的784个像素值，并将其馈送至Dense层中。此外，需要将标签变换为独热编码
    # 使用reshape方法对输入数据集进行形状变换,np.reshape()将给定形状的数组转换为不同的形状。
    num_pixels = x_train.shape[1] * x_train.shape[2]
    x_train = x_train.reshape(-1, num_pixels).astype('float32')
    x_test = x_test.reshape(-1, num_pixels).astype('float32')

    # 4.将标签变换为独热编码
    y_train = np_utils.to_categorical(y_train)  # categorical:分类
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_test.shape[1]
    print(num_classes)

    # 5.用具有1000个节点的隐藏层构建神经网络    input_dim:输入数据的维度
    # 输入具有28x28=784个值，这些值与隐藏层中的1000个节点单元相连，指定激活函数为relu，
    # 最后，隐藏层连接到具有num_classes=10个值的输出，在输出的之前使用softmax激活函数，以便获得图像的类别概率
    model = Sequential()
    model.add(Dense(1000, input_dim=num_pixels, activation='relu'))
    model.add(Dense(num_classes, activation='softmax'))

    # 6.模型架构信息可视化
    model.summary()

    # 7.编译模型
    # 多分类交叉熵函数，Adam优化器  检测准确率accuracy(acc)
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['acc'])

    # 8.拟合模型
    history = model.fit(x_train, y_train,
                        validation_data=(x_test, y_test),
                        epochs=5,  # epochs=50
                        batch_size=64,
                        verbose=1)

    # 9.提取不同epoch的训练和测试损失值以及准确率指标
    # 在拟合模型时,history变量会在训练和测试数据集的每个epoch中存储相对应的准确率和损失值，我们将这些值提取存储在列表中，以便绘制在训练数据集和测试数据集中准确率和损失的变化
    history_dict = history.history
    loss_values = history_dict['loss']
    val_loss_values = history_dict['val_loss']
    acc_values = history_dict['acc']
    val_acc_values = history_dict['val_acc']
    epochs = range(1, len(val_acc_values) + 1)

    # 10.可视化不同epoch的训练和测试损失以及准确性
    # 随着epoch数的增加，训练和测试的损失值变化
    plt.subplot(211)
    plt.plot(epochs, loss_values, marker='x', label='Train loss')
    plt.plot(epochs, val_loss_values, marker='o', label='Test loss')
    plt.title('Training and test loss')
    plt.xlabel('Epochs')
    plt.ylabel('loss')
    # plt.legend():给图像加图例，位于地图一角或一侧的地图上各种符号和颜色所代表内容与指标的说明，有助于更好地认识地图
    plt.legend()

    # 随着epoch数的增加，训练和测试的准确性
    plt.subplot(212)
    plt.plot(epochs, acc_values, marker='x', label="Training accuracy")
    plt.plot(epochs, val_acc_values, marker='o', label='Test accuracy')
    plt.title('Training and test accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.show()

    # 11.手动计算最终模型在测试集上的准确率
    # 使用模型的predict方法计算给定输入(x_test)的预测输出值。然后，我们循环所有测试集的预测结果，使用argmax计算具有最高概率值的索引。
    # 同时。对测试数据集的真实标签执行相同的操作。在测试数据集的预测值和真实值中，最高概率值的孙银相同表示预测正确，在测试数据集中正确预测的数量除以测试数据集的数据总量即为模型的准确率
    predicts = model.predict(x_test)
    correct = 0
    for i in range(len(x_test)):
        predict = np.argmax(predicts[i], axis=0)
        act = np.argmax(y_test[i], axis=0)
        if predict == act:
            correct += 1
        else:
            continue
    accuracy = correct / len(x_test)
    print('Test accuracy: {:.4f}%'.format(accuracy * 100))


# fun2()


def main():
    p = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    p.add_argument('--fun_choose', '-fc', type=str,
                   default=1, help='choose the feature you want to execute')
    args = p.parse_args()

    fc = args.fun_choose
    if fc == '1':
        fun1()
    elif fc == '2':
        fun2()
    else:
        raise "错误选择"


main()
